package com.dly.blog.utile;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.util.Assert;

import java.util.Map;

@Slf4j
public final class SpringContextHolder {

    private static ApplicationContext applicationContext;
    private static ApplicationContext parentApplicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Assert.notNull(applicationContext, "SpringContextHolder injection ApplicationContext is null");
        SpringContextHolder.applicationContext = applicationContext;
        SpringContextHolder.parentApplicationContext = applicationContext.getParent();
    }

    /**
     * 获取Spring上下文
     * @return Spring上下文
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过Spring Bean名称获取Spring Bean
     * @param beanName Spring Bean名称
     * @param <T> 返回的spring bean的数据类型
     * @return Spring Bean
     */
    public static <T>T getBean(String beanName) {
        Assert.hasText(beanName, "SpringContextHolder name is null or empty");
        try {
            return (T) applicationContext.getBean(beanName);
        } catch (Exception e) {
            return (T) parentApplicationContext.getBean(beanName);
        }
    }

    /**
     * 通过Bean的名称和Bean对应的class获取具体的Bean【这个主要用于一个接口被注入多个实现类的情况】
     * @param beanName Spring Bean对应的名称
     * @param beanClass Spring Bean对应的Class
     * @param <T> Bean对应的类型
     * @return Spring中注入的Bean
     */
    public static <T> T getBean(String beanName, Class<T> beanClass) {
        Assert.hasText(beanName, "SpringContextHolder beanName is null or empty");
        Assert.notNull(beanClass, "SpringContextHolder beanClass is null");
        try {
            return applicationContext.getBean(beanName, beanClass);
        } catch (Exception e) {
            return parentApplicationContext.getBean(beanName, beanClass);
        }
    }
    /**
     * 通过Spring Bean类获取对应Spring Bean
     * @param beanClass Spring中注入的Bean对应的Class
     * @param <T> beanClass对应的类型
     * @return Spring中注入的Bean
     */
    public static <T> T getBean(Class<T> beanClass) {
        Assert.notNull(beanClass, "SpringContextHolder beanClass is null");
        try {
            return applicationContext.getBean(beanClass);
        } catch (Exception e) {
            return parentApplicationContext.getBean(beanClass);
        }
    }

    /**
     * 通过Bean Class获取该Class所有的Spring Bean
     * @param beanClass 注入的Bean Class类型
     * @param <T> 类型
     * @return bean class注入的bean，注入名称-注入类
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> beanClass) {
        Assert.notNull(beanClass, "SpringContextHolder beanClass is null");
        try {
            return applicationContext.getBeansOfType(beanClass);
        } catch (Exception e) {
            return parentApplicationContext.getBeansOfType(beanClass);
        }
    }

    /**
     * 发布Spring事件
     * @param event 继承{@link org.springframework.context.event.ApplicationContextEvent}的类
     */
    public static void publishEvent(ApplicationEvent event) {
        Assert.notNull(event, "SpringContextHolder event is null");
        try {
            applicationContext.publishEvent(event);
        }catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * 通过SPEL获取ApplicationContext的值，解析表达式
     * @param expression 表达式
     * @return 解析后的值
     */
    public static <T> T parseExpression(String expression) {
        Assert.notNull(expression, "SpringContextHolder expression is null");
        return (T) applicationContext.getEnvironment().resolveRequiredPlaceholders(expression);
    }

    private SpringContextHolder(){}
    /**
     * 单例Holder模式： 优点：将懒加载和线程安全完美结合的一种方式（无锁）。（推荐）
     *
     * @return 实实例
     */
    public static SpringContextHolder getInstance() {
        return SpringUtilsHolder.INSTANCE;
    }

    private static class SpringUtilsHolder {
        public static final SpringContextHolder INSTANCE = new SpringContextHolder();
    }
}